﻿using ComponentFactory.Krypton.Toolkit;
using StableDiffusionTools.Framework;
using StableDiffusionTools.Interop;
using StableDiffusionTools.RTF;
using StableDiffusionTools.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using TaggingToolkit.Events;
using TaggingToolkit.Framework;
using Operation = StableDiffusionTools.Framework.Operation;

namespace TaggingToolkit.Forms
{
    public delegate void MarkEventDelegate(object sender, MarkEventArgs e);
    public delegate void ReplaceEventDelegate(object sender, ReplaceEventArgs e);
    public delegate void RequestTagsFromParentDelegate(object sender, RequestTagsEventArgs e);

    public partial class TagSearchReplaceForm : KryptonForm
    {
        public event MarkEventDelegate MarkedEvent;
        protected void OnMarkedEvent(IEnumerable<string> str = null, IEnumerable<string> regex = null)
        {
            MarkedEvent?.Invoke(this, new MarkEventArgs(str, regex));
        }

        public event EventHandler UnmarkedEvent;
        protected void OnUnmarkedEvent()
        {
            UnmarkedEvent?.Invoke(this, new EventArgs());
        }

        public event ReplaceEventDelegate ReplaceEvent;
        protected void OnReplaceEvent(string from, string to, bool selected, out int tagsCount, out int tagsMatched)
        {
            var args = new ReplaceEventArgs(from, to, selected);
            ReplaceEvent?.Invoke(this, args);
            tagsCount = args.TagsCount;
            tagsMatched = args.TagsMatched;
        }

        public event RequestTagsFromParentDelegate RequestTagsFromParent;
        protected IList<DataTags> OnRequestTagsFromParent()
        {
            var args = new RequestTagsEventArgs() { Selected = kryptonCHKOnlySelected.Checked };
            RequestTagsFromParent?.Invoke(this, args);
            return args.Tags;
        }

        TaggingToolkitSettings settings;

        public TagSearchReplaceForm(TaggingToolkitSettings settings)
        {
            InitializeComponent();
            Icon = Properties.Resources.text_replace;
            this.settings = settings;

            Load += (s, e) =>
            {
                if (Owner != null)
                {
                    CenterToParent();
                }
            };

            KeyDown += (s, e) =>
            {
                if (e.KeyCode == Keys.Escape)
                {
                    e.SuppressKeyPress = true;
                    Visible = false;
                }
            };

            StartPosition = FormStartPosition.CenterParent;

            KryptonManager.GlobalPaletteChanged += new EventHandler(OnGlobalPaletteChanged);
        }

        private void OnGlobalPaletteChanged(object sender, EventArgs e)
        {
            Preview();
        }

        private void kryptonTBFrom_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                e.SuppressKeyPress = true;
                Preview();
            }
        }

        private void kryptonTBTo_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                e.SuppressKeyPress = true;
                Preview();
            }
        }

        private void kryptonBTNSwitchFields_Click(object sender, EventArgs e)
        {
            string temp = kryptonTBTo.Text;
            kryptonTBTo.Text = kryptonTBFrom.Text;
            kryptonTBFrom.Text = temp;
        }

        private void kryptonBTNPreview_Click(object sender, EventArgs e)
        {
            Preview();
        }

        private void kryptonBTNReplace_Click(object sender, EventArgs e)
        {
            string from = kryptonTBFrom.Text;

            if (from.Length == 0)
            {
                tsslStatus.Text = $"Can't match empty {(kryptonCHKRegex.Checked ? "regex" : "string")}";
                kryptonRichTextBox1.Clear();
                return;
            }

            string to = kryptonTBTo.Text;

            OnReplaceEvent(from, to, kryptonCHKOnlySelected.Checked, out int tagsCount, out int tagsMatched);
            tsslStatus.Text = $"Replaced {tagsMatched}/{tagsCount} matches";
        }

        private void kryptonBTNMark_Click(object sender, EventArgs e)
        {
            OnMarkedEvent(kryptonCHKRegex.Checked ? null : new string[] { kryptonTBFrom.Text }, kryptonCHKRegex.Checked ? new string[] { kryptonTBFrom.Text } : null);
        }

        private void kryptonBTNUnmark_Click(object sender, EventArgs e)
        {
            OnUnmarkedEvent();
        }

        private void Preview()
        {
            string from = kryptonTBFrom.Text;

            if (from.Length == 0)
            {
                tsslStatus.Text = $"Can't match empty {(kryptonCHKRegex.Checked ? "regex" : "string")}";
                kryptonRichTextBox1.Clear();
                return;
            }

            IList<DataTags> tags = OnRequestTagsFromParent();
            Color textColor = Color.FromKnownColor(KnownColor.ControlText);
            Color textColorInserted = settings.SearchReplaceInsertColor;
            Color textColorRemoved = settings.SearchReplaceRemoveColor;
            Color backColor = KryptonManager.CurrentGlobalPalette.GetBackColor1(PaletteBackStyle.InputControlStandalone, PaletteState.Normal);
            string to = kryptonTBTo.Text;

            RTFormatter cFormat = new RTFormatter
            {
                TextColor = textColor,
                BackColor = backColor,
                //DefaultFontSize = 17,
                //DefaultFontName = "Courier New"
            };

            bool doRegex = kryptonCHKRegex.Checked;
            Regex regex = null;
            if (doRegex)
            {
                try
                {
                    regex = new Regex(from, RegexOptions.None, TimeSpan.FromSeconds(1));
                }
                catch (Exception ex)
                {
                    tsslStatus.Text = $"Regex Error: {ex.Message}";
                    kryptonRichTextBox1.Clear();
                    return;
                }
            }

            bool first = true;
            int matchesFound = 0;
            foreach (var item in tags)
            {
                bool hasMatch;
                MatchCollection matches = null;
                if (doRegex)
                {
                    matches = regex.Matches(item.TagData);
                    hasMatch = matches.Count > 0;
                }
                else
                {
                    hasMatch = item.TagData.Contains(from);
                }

                if (hasMatch)
                {
                    matchesFound++;

                    if (first)
                        first = false;
                    else
                        cFormat.AppendLine();

                    cFormat.TextColor = Color.LightSlateGray;
                    cFormat.BackColor = backColor;
                    cFormat.AppendText(item.FullPath);
                    cFormat.TextColor = textColor;
                    cFormat.AppendLine();

                    string oldStr = item.TagData;
                    string newStr = null;
                    if (doRegex)
                    {
                        StringBuilder sb = new StringBuilder(oldStr);
                        for (int i = matches.Count - 1; i >= 0; i--)
                        {
                            Match match = matches[i];
                            sb.Remove(match.Index, match.Length);
                            sb.Insert(match.Index, to);
                        }
                        newStr = sb.ToString();
                    }
                    else
                    {
                        newStr = oldStr.Replace(from, to);
                    }

                    diff_match_patch dmp = new diff_match_patch();
                    List<Diff> diff = dmp.diff_main(oldStr, newStr);
                    dmp.diff_cleanupSemantic(diff);
                    for (int i = 0; i < diff.Count; i++)
                    {
                        switch (diff[i].operation)
                        {
                            case Operation.DELETE:
                                cFormat.BackColor = textColorRemoved;
                                cFormat.AppendText(diff[i].text);
                                break;
                            case Operation.EQUAL:
                                cFormat.BackColor = backColor;
                                cFormat.AppendText(diff[i].text);
                                break;
                        }
                    }
                    cFormat.AppendLine();
                    for (int i = 0; i < diff.Count; i++)
                    {
                        switch (diff[i].operation)
                        {
                            case Operation.INSERT:
                                cFormat.BackColor = textColorInserted;
                                cFormat.AppendText(diff[i].text);
                                break;
                            case Operation.EQUAL:
                                cFormat.BackColor = backColor;
                                cFormat.AppendText(diff[i].text);
                                break;
                        }
                    }
                    cFormat.AppendLine();
                }
            }

            tsslStatus.Text = $"{matchesFound}/{tags.Count} {(kryptonCHKOnlySelected.Checked ? "(Selected)" : "")} Tags files matched";

            //Get scroll pos prior to assigning new text
            //var pos = User32.GetScrollPos(kryptonRichTextBox1.Handle);
            //pos.Offset(0, kryptonRichTextBox1.Height);

            kryptonRichTextBox1.Rtf = cFormat.ToString();

            ////last character Y - first character y
            //var maxscroll = Math.Abs(kryptonRichTextBox1.GetPositionFromCharIndex(kryptonRichTextBox1.TextLength).Y - kryptonRichTextBox1.GetPositionFromCharIndex(0).Y);

            ////workaround for bug in RTB
            ////if scrolled past end and new text is smaller, scroll new content into view
            //if (pos.Y > 0 && pos.Y > maxscroll && pos.Y - maxscroll > 20)
            //{
            //    maxscroll = Math.Abs(kryptonRichTextBox1.GetPositionFromCharIndex(kryptonRichTextBox1.TextLength).Y - kryptonRichTextBox1.GetPositionFromCharIndex(0).Y);
            //    User32.SetScrollPos(kryptonRichTextBox1.Handle, new Point(pos.X, maxscroll - kryptonRichTextBox1.ClientSize.Height));
            //}
        }
    }
}
